Unreal Engine 5 C++ The Ultimate Game Developer Course 17-
Chapter17
敵 AI を Navigation Mesh で実装する
NavMeshBoundsVolume を 地面に配置して、P を押すと範囲が緑色で表示される
BP_EnemyBase を開くと、AI Controller Class という設定値が存在するのでこちらを操作して敵を制御する。
Auto Possess の設定。Player に設定するとゲーム開始時に自分が敵視点となるので、DIsable 。Auto Possess AI の項目があるので、Placed in World or Spawned とする(今後 Spawner から産ませることになるのでそちらも考慮する)。GetAIController()->MoveToLocationとすると、NavMeshBoundsVolume 等を考慮して UE 側が経路探索して進行してくれる
@ show navigationで NavMeshBoundsVolume の確認ができる。壺を壊した後、Vol が空いたままになっていたり、壺と円柱の間に一定の間があるにもかかわらず、遠回りするようになっている
https://scrapbox.io/files/6a1cc83665e24daf8cc67f8c.png
Project Settings -> Navigation Mesh で、Runtime Generation を Static -> Dynamic に変更し、動的に変わるようにする
https://scrapbox.io/files/6a1cc8c965e24daf8cc68070.png
スキマの調整は、cell size で行う。またいでほしい高さを変えるときは、cell height で調整。
https://scrapbox.io/files/6a1cca3565e24daf8cc68321.png
Blend Space
敵の Idle -> Walk 状態の切り分けに Blend Space を使う。1DはHorizontal のみ、通常のものは 縦軸 (Vertical) も考慮できる。今回は横軸の動きだけ意識すればいいので、グラフの上下は特に気にしなくてよい( Y = 0 で設定しておくとよい)
Horizontal Axis の値をGroundSpeedという値で、上限値などをつけて管理。この上限値は Character Movement の Max Walk Speed と合わせる。
GroundSpeed の設定
ABP で GroundSpeed 変数を作る
これは Character Movement から取得する必要があるので、Initialize Animation の段階で Character Movement Component を取得しておく
BP Thread Safe Update で、Property Access で Character Movement.Velocity を設定し、float に変換して動的割り当て
https://scrapbox.io/files/6a1cdd1b65e24daf8cc6bf7e.png
変数として設定した GroundSpeed を、Blend Space の GroundSpeed として使うように設定。
https://scrapbox.io/files/6a1cdd7e65e24daf8cc6c09e.png
敵が方向転換するときにかくかくする対策
code:EnemyBase.cpp
GetCharacterMovement()->bOrientRotationToMovement = true;
bUseControllerRotationYaw = false;
bUseControllerRotationPitch = false;
bUseControllerRotationRoll = false;
徘徊処理(Patrol)
パトロール <-> 敵にめがけて走る処理
エラー
1>1/4 Compile x64 EnemyBase.cpp 1>C:\Users\watas\Ue5Project\LinearDungeon\Source\LinearDungeon\Private\Enemies\EnemyBase.cpp(62): error C2027: use of undefined type 'FPathFollowingRequestResult'
1>C:\Program Files\Epic Games\UE_5.5\Engine\Source\Runtime\AIModule\Classes\AIController.h(47): note: see declaration of 'FPathFollowingRequestResult'
講義内容とは別に、include しなければならないファイルがいくつかある。適宜追加
エラー2
UE が落ちる
0x0000011141cda303 UnrealEditor-LinearDungeon.dll!AEnemyBase::BeginPlay()
0x00007ffca4a5e957 KERNEL32.DLL!UnknownFunction []
https://scrapbox.io/files/6a1d4a0765e24daf8cc81cd0.png
Target として円柱を指定したが、緑色の部分が当然くりぬかれているので到達不可能でクラッシュしている。検証の場合は、素直にTargetPoint 等を配置して検証しよう
https://scrapbox.io/files/6a1d4a8865e24daf8cc81dc9.png
よけるような形で処理が進む
code:EnemyBase.cpp
# h
// ===== 徘徊処理(Patrol) =====
UPROPERTY(EditInstanceOnly, Category = "AI Navigation")
TObjectPtr<AActor> PatrolTarget;
UPROPERTY(EditInstanceOnly, Category = "AI Navigation")
TArray<AActor*> PatrolTargets;
TObjectPtr<AAIController> EnemyController;
# cpp BeginPlay()
// 敵 移動処理テストコード
// Controller を取得し、AIController にキャスト(Player Controller なら失敗する形になる)
EnemyController = Cast<AAIController>(GetController());
if (EnemyController && PatrolTarget)
{
FAIMoveRequest MoveRequest; // AI に対する移動の要求内容をまとめた Struct
MoveRequest.SetGoalActor(PatrolTarget); // 目的地を特定の Actor に設定(Actor が動けば、併せて追跡するように再計算する)
MoveRequest.SetAcceptanceRadius(15.f); // 目的地に着いたと許容する半径
// FNavigationPath クラスのスマートポインタ
// MoveTo で計算した、現在地から目的地までの経路データを受け取るための変数
FNavPathSharedPtr NavPath;
FPathFollowingRequestResult Result = EnemyController->MoveTo(MoveRequest, &NavPath); // 経路検索の結果を Result で受け取る
// 生成された経路から、パスの頂点リストを取得
// リストを for で回して、計算結果の頂点を Sphere で出したもの
TArray<FNavPathPoint>& PathPoints = NavPath->GetPathPoints();
for (auto& Point : PathPoints)
{
const FVector& Location = Point.Location;
DrawDebugSphere(GetWorld(), Location, 12.f, 12, FColor::Green, false, 10.f);
}
}
配列で徘徊させる
指定のポイントまでついたとき、少し止まらせる
Timer を使う。
Pawn を感知したら、敵が追いかけるようにする
視野などを決定できる感知用の Component である、Pawn Sensing が用意されているのでそれを使う
イメージ
https://scrapbox.io/files/6a1e174d65e24daf8cc9415c.png
UPawnSensingComponent はもう古い要素なので、代替する
C:\Users\watas\Ue5Project\LinearDungeon\Source\LinearDungeon\Private\Enemies\EnemyBase.cpp(39): warning C4996: 'UPawnSensingComponent': Pawn sensing is deprecated. AI Perception should be used instead. Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.
C:\Users\watas\Ue5Project\LinearDungeon\Source\LinearDungeon\Private\Enemies\EnemyBase.cpp(56): warning C4996: 'UPawnSensingComponent::OnSeePawn': Pawn sensing is deprecated. AI Perception should be used instead. Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.
AI Perceptionshould be used instead の通り、こちらを使う
code:cpp
# h
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UAIPerceptionComponent> AIPerceptionComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UAISenseConfig_Sight> SightConfig; // 視覚設定
UFUNCTION()
void OnTargetDetected(AActor* Actor, FAIStimulus Stimulus);
# cpp
if (SightConfig)
{
// 視覚パラメータの初期設定
SightConfig->SightRadius = 1000.f;// 視認可能な半径
SightConfig->LoseSightRadius = 1200.f; // 見失う半径(チラつき防止のため少し大きめに設定する)
SightConfig->PeripheralVisionAngleDegrees = 90.f; // 視野角
SightConfig->SetMaxAge(5.f);// 記憶保持時間
// どの所属(敵、味方、中立)を検知するか
// デフォルトでは全て検知しない設定のため、明示的にtrueにする
SightConfig->DetectionByAffiliation.bDetectEnemies = true;
SightConfig->DetectionByAffiliation.bDetectNeutrals = true;
SightConfig->DetectionByAffiliation.bDetectFriendlies = true;
// Perceptionコンポーネントに設定を登録
AIPerceptionComponent->ConfigureSense(*SightConfig);
AIPerceptionComponent->SetDominantSense(SightConfig->GetSenseImplementation());
}
void AEnemyBase::BeginPlay()
{
Super::BeginPlay();
// デリゲート登録
if (AIPerceptionComponent)
AIPerceptionComponent->OnTargetPerceptionUpdated.AddDynamic(this, &AEnemyBase::OnTargetDetected);
// ...
void AEnemyBase::OnTargetDetected(AActor* Actor, FAIStimulus Stimulus)
{
UE_LOGFMT(LogTemp, Warning, "AEnemyBase::OnTargetDetected()");
if (!Actor) return;
// 発見したか、見失ったのかを判定
if (Stimulus.WasSuccessfullySensed())
{
UE_LOGFMT(LogTemp, Log, "Detected target: {0}", Actor->GetName());
// TODO: 追跡状態へ移行する処理など
}
else
{
UE_LOGFMT(LogTemp, Log, "Lost sight of target: {0}", Actor->GetName());
// TODO: 見失ったあとの処理(数秒待機してPatrolに戻るなど)
BP でも設定できる
https://scrapbox.io/files/6a1e240565e24daf8cc9644e.png
GamePlay Debugger
\に割り当てた。Play 中に押下することで起動。
https://scrapbox.io/files/6a1e234565e24daf8cc961db.png
視野が確認できたり、細かい判定が見える
4キーに割り当てた、Perceptionを緑色にして有効化する必要がある。
EnemyState と検知処理
検知処理
code:cpp
void AEnemyBase::OnTargetDetected(AActor* Actor, FAIStimulus Stimulus)
if (! Cast<ALinearPlayerCharacter>(Actor)) return;
とすることで、LPCharacter 以外を無視できるが、TIck で呼ばれる関数で Cast は使いたくない。
Tag を見るようにする
code:cpp
void ALinearPlayerCharacter::BeginPlay()
{
Super::BeginPlay();
Tags.Add(FName("LinearPlayerCharacter"));
}
void AEnemyBase::OnTargetDetected(AActor* Actor, FAIStimulus Stimulus)
if (Actor->ActorHasTag(FName("LinearPlayerCharacter"))) {
// ...
攻撃されたら、State を変える
Section 18 - 19
敵とLPCharacter の共通化や調整
一旦、自分のプロジェクトではやらない。自前でこの辺りは設計してみる
Section 20
キャラクターの HealthBar を用意する
とりあえず貰い物の HUD で設計していこう(後から変えればよいので、汎用的な使い方から)
UserInterface2D に Compression してから行う
フォントを探す
noto serif をつかってみよう。ttf を UE5 に import
UserWidget C++ class を作る
LinearDungeon\Public\Components\HUD\HealthBar.h(敵のもの)と同じように作る
ULinearDungeonOverlay : public UUserWidgetとした
作ったのち、WBP_LinearDungeonOverlay の親クラスをこちらに変更
作った HUD を画面に出す
https://scrapbox.io/files/6a293f1a601c8b4efdd6f02b.png
Open Level Blueprint という設定で、どの HUD を出すか決めているが、これをGamemodeで実現する
HUD 用意
BP > HUD から、BP_LDHUDを用意
https://scrapbox.io/files/6a29510b601c8b4efdd72b9b.png
かなり昔に作った、BP_LinearGameModeというGameMode C++ から作ったクラスがあるので、そこの HUDを作った BP HUD を設定(スポーンさせる Actor などを選ぶのと同じ)
BP > HUDを親として作ったが、自作の C++ Class をベースに作り直してみよう
やること
BeginPlayを弄る
Create Overlay Widget で、何のクラスを作るか指定
AddToViewport で設計
全体の流れは以下の通り
LinearGameMode(殆ど書いてない)C++ から、BP_LinearGameModeを作成。これは昔やった
HUD では、Create Widget が使える。そこで Create できる対象に、UserWidget がある
C++ クラスで、体力バーやポイズバーとつなげたULinearDungeonOverlayを用意
このクラスをベースに、WBP_LinearDungeonOverlay を作る。変数名と、WBP Canvas 側で作ったプログレスバーとを紐づけ。
BP_LinearGameMode で設定する HUD 作り
最初は、標準クラスの HUDを基に BP を作って、ノードで↑のように Viewport に渡した
ただ、ノードで行う必要もないので、HUD を親にして C++ を自作して、その中で Viewport に渡すようにする
BP_LinearGameMode の HUD に、BP_LinearDungeonHUD を割り当て。(HUD の中では、BeginPlay 時に LinearDungeonOverlay を Viewport に割り当てるようにしているという流れ)
Character から HUD の持つ Overlay にアクセスして、ULinearDungeonOverlayのSetHealthBarPercent()等を使う
終わったらやりたいこと
コイン集めゲーム ✅ 作った
軽い脱出ゲームとかもいいかも
NPCみたいな人に喋らせるゲーム ✅ 作った
映像ではない、プチムービーシーンなどでキャラが勝手に動いたりする描写がどう作られているのか調べる